Skip to content

fix(gemini): strip JSON-Schema keywords the tool API rejects - #4761

Merged
georgi merged 1 commit into
mainfrom
claude/gemini-api-json-error-dn2oc0
Aug 6, 2026
Merged

fix(gemini): strip JSON-Schema keywords the tool API rejects#4761
georgi merged 1 commit into
mainfrom
claude/gemini-api-json-error-dn2oc0

Conversation

@georgi

@georgi georgi commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Problem

Gemini 400s the whole tool batch on a JSON-Schema keyword it has no field for:

Invalid JSON payload received. Unknown name "const" at
'tools[0].function_declarations[21].parameters.properties[3].value.items
 .properties[6].value.properties[0].value': Cannot find field.

Gemini's function-declaration schema is an OpenAPI 3.0 subset. Zod 4's z.toJSONSchema (draft 2020-12, used by zodToJsonSchema in packages/runtime/src/zod-schema.ts) emits const for every z.literal() and $ref + $defs for every reused schema — so any tool defined in Zod hit this. One bad keyword anywhere in the tree aborts every tool in the request, not just the offending one.

sanitizeGeminiSchema already stripped a handful of keywords (additionalProperties, $ref, patternProperties, …) but not const, and stripping $ref while dropping $defs left an empty schema behind.

Changes

packages/runtime/src/providers/gemini-provider.ts:

  • const → an accepted form. A string literal becomes a single-value enum; a number or boolean literal becomes its type plus a Must be X. description hint, since Gemini's enum takes strings only.
  • $ref inlined against the root document before $defs is dropped. A cyclic or unresolvable ref degrades to { type: "object" } rather than {}.
  • oneOfanyOf (same meaning to the model, and anyOf is in Gemini's dialect); allOf folded into the parent, merging properties and unioning required, with parent keys winning.
  • ["string","null"]type: "string" + nullable: true — Gemini's type is one string, and the union is Zod's nullable shape.
  • Remaining draft-2020-12 keywords dropped: not, if/then/else, prefixItems, contains, uniqueItems, multipleOf, examples, readOnly/writeOnly, deprecated, contentEncoding/contentMediaType, $comment, $defs.
  • Recursion is now schema-aware. Keyword filtering no longer applies to property names, so a property called const (or additionalProperties) survives, and data under enum/default/example is copied verbatim instead of being walked as a schema.

Existing behavior kept: tool-name sanitization, and the items backfill for array schemas.

Tests

Five cases added to packages/runtime/tests/providers/gemini-provider.test.ts: const rewriting for string/number/boolean literals, a property literally named const, $ref inlining with $defs removal, a recursive $ref, and the oneOf/allOf/nullable-union mapping. npx vitest run tests/providers/gemini-provider*.test.ts — 107 passed. npm run lint --workspace=packages/runtime (tsc) clean.

Unrelated pre-existing failures in tests/providers (fal, kie, atlascloud, manifest-models, replicate, together, topaz) are present on main in this environment and untouched by this change.

🤖 Generated with Claude Code

https://claude.ai/code/session_0174zpe4326XDbrzA4UANeQR


Generated by Claude Code

Gemini's function-declaration schema is an OpenAPI 3.0 subset. Zod 4's
`z.toJSONSchema` emits `const` for every `z.literal()` and `$ref`/`$defs`
for every reused schema, so any tool defined in Zod produced a 400
("Unknown name \"const\"") that aborted the whole tool batch.

The sanitizer now:
- rewrites `const` to a single-value `enum` for strings, and to a type
  plus a description hint for numbers and booleans (Gemini's `enum` only
  takes strings)
- inlines local `$ref`s before dropping `$defs`, degrading cyclic or
  unresolvable refs to a permissive object instead of an empty schema
- maps `oneOf` onto `anyOf` and folds `allOf` members into the parent
- collapses a `["string","null"]` type union onto `type` + `nullable`
- drops the remaining draft-2020-12 keywords Gemini has no field for
- recurses schema-aware, so a property named `const` (or any other
  keyword) and data under `enum`/`default`/`example` survive intact

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174zpe4326XDbrzA4UANeQR
@georgi
georgi enabled auto-merge (squash) August 6, 2026 11:16
@georgi
georgi merged commit 8010a3a into main Aug 6, 2026
23 checks passed
@georgi
georgi deleted the claude/gemini-api-json-error-dn2oc0 branch August 6, 2026 11:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants